﻿/*
	Copyright (c) 2013 Andrew Meyer

	Permission is hereby granted, free of charge, to any person obtaining a copy of this software
	and associated documentation files (the "Software"), to deal in the Software without
	restriction, including without limitation the right to use, copy, modify, merge, publish,
	distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
	Software is furnished to do so, subject to the following conditions:

	The above copyright notice and this permission notice shall be included in all copies or
	substantial portions of the Software.

	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
	BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
	NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
	DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
	OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

using System;
using System.Collections.Generic;
using System.Windows.Media;
using System.Windows.Media.Media3D;

namespace Ascend
{
	/// <summary>
	/// Helper class for generating basic three-dimensional geometric models.
	/// </summary>
	public static class ModelMaker
	{
		#region Public Methods
		/// <summary>
		/// Generates a triangle from the given points and color.
		/// </summary>
		/// <param name="p0">
		/// The first point in the triangle.
		/// </param>
		/// <param name="p1">
		/// The second point in the triangle.
		/// </param>
		/// <param name="p2">
		/// The third point in the triangle.
		/// </param>
		/// <param name="color">
		/// The color of the triangle.
		/// </param>
		/// <param name="doubleSided">
		/// Whether the triangle should be double sided.
		/// </param>
		/// <returns>
		/// Returns a System.Windows.Media.Media3D.GeometryModel3D representing the triangle.
		/// </returns>
		public static GeometryModel3D MakeTriangle(Point3D p0, Point3D p1, Point3D p2, Color color,
			bool doubleSided = false)
		{
			MeshGeometry3D mesh = new MeshGeometry3D();

			mesh.Positions = new Point3DCollection
			{
				p0, p1, p2
			};

			Material material = new DiffuseMaterial(new SolidColorBrush(color));

			return new GeometryModel3D(mesh, material)
			{
				BackMaterial = doubleSided
					? material
					: null
			};
		}

		/// <summary>
		/// Generates a cube of the given size and color.
		/// </summary>
		/// <param name="sizeX">
		/// The size of the cube in its X-dimension.
		/// </param>
		/// <param name="sizeY">
		/// The size of the cube in its Y-dimension.
		/// </param>
		/// <param name="sizeZ">
		/// The size of the cube in its Z-dimension.
		/// </param>
		/// <param name="color">
		/// The color of the cube.
		/// </param>
		/// <returns>
		/// Returns a System.Windows.Media.Media3D.GeometryModel3D representing the cube.
		/// </returns>
		public static GeometryModel3D MakeCube(double sizeX, double sizeY, double sizeZ,
			Color color)
		{
			double halfX = sizeX / 2;
			double halfY = sizeY / 2;
			double halfZ = sizeZ / 2;

			Point3D[] vertices =
			{
				new Point3D(-halfX, -halfY, halfZ),
				new Point3D(halfX, -halfY, halfZ),
				new Point3D(halfX, halfY, halfZ),
				new	Point3D(-halfX, halfY, halfZ),
				new Point3D(-halfX, -halfY, -halfZ),
				new Point3D(halfX, -halfY, -halfZ),
				new Point3D(halfX, halfY, -halfZ),
				new	Point3D(-halfX, halfY, -halfZ)
			};

			MeshGeometry3D mesh = new MeshGeometry3D();

			mesh.Positions = new Point3DCollection
			{
				vertices[0], vertices[1], vertices[2], vertices[3],
				vertices[1], vertices[5], vertices[6], vertices[2],
				vertices[5], vertices[4], vertices[7], vertices[6],
				vertices[4], vertices[0], vertices[3], vertices[7],
				vertices[3], vertices[2], vertices[6], vertices[7],
				vertices[4], vertices[5], vertices[1], vertices[0]
			};

			mesh.TriangleIndices = new Int32Collection
			{
				0, 1, 2, 2, 3, 0,
				4, 5, 6, 6, 7, 4,
				8, 9, 10, 10, 11, 8,
				12, 13, 14, 14, 15, 12,
				16, 17, 18, 18, 19, 16,
				20, 21, 22, 22, 23, 20
			};

			return new GeometryModel3D(mesh, new DiffuseMaterial(new SolidColorBrush(color)));
		}

		/// <summary>
		/// Generates a sphere of given radius, resolution, and color.
		/// </summary>
		/// <param name="radius">
		/// The radius of the sphere.
		/// </param>
		/// <param name="slices">
		/// The number of divisions around the sphere's central axis.
		/// </param>
		/// <param name="rings">
		/// The number of divisions between the top and bottom of the sphere.
		/// </param>
		/// <param name="color">
		/// The color of the sphere.
		/// </param>
		/// <returns>
		/// Returns a System.Windows.Media.Media3D.GeometryModel3D representing the sphere.
		/// </returns>
		public static GeometryModel3D MakeSphere(double radius, int slices, int rings, Color color)
		{
			List<Point3D> positions = new List<Point3D>();
			List<int> indices = new List<int>();
			double deltaAlpha = Math.PI / rings;
			double deltaTheta = 2 * Math.PI / slices;
			int lastIndex = slices * (rings - 1) + 1;

			positions.Add(new Point3D(0, radius, 0));

			for (int r = 1; r < rings; r++)
			{
				double alpha = (Math.PI / 2) - r * deltaAlpha;
				double y = radius * Math.Sin(alpha);
				double currRad = radius * Math.Cos(alpha);

				for (int s = 0; s < slices; s++)
				{
					double theta = s * deltaTheta;

					positions.Add(
						new Point3D(currRad * Math.Cos(theta), y, currRad * Math.Sin(theta)));

					int i0 = 1 + (r - 1) * slices + s;
					int i1 = (s == slices - 1)
						? i0 - slices + 1
						: i0 + 1;
					int i2 = i1 + slices;
					int i3 = i0 + slices;

					if (r < rings - 1)
					{
						indices.Add(i0);
						indices.Add(i1);
						indices.Add(i2);
						indices.Add(i2);
						indices.Add(i3);
						indices.Add(i0);
					}

					if (r == 1)
					{
						indices.Add(0);
						indices.Add(i1);
						indices.Add(i0);
					}

					if (r == rings - 1)
					{
						indices.Add(i0);
						indices.Add(i1);
						indices.Add(lastIndex);
					}
				}
			}

			positions.Add(new Point3D(0, -radius, 0));

			return new GeometryModel3D(
				new MeshGeometry3D
				{
					Positions = new Point3DCollection(positions),
					TriangleIndices = new Int32Collection(indices)
				},
				new DiffuseMaterial(new SolidColorBrush(color)));
		}

		/// <summary>
		/// Generates a 16-by-8 resolution sphere of given radius and color.
		/// </summary>
		/// <param name="radius">
		/// The radius of the sphere.
		/// </param>
		/// <param name="color">
		/// The color of the sphere.
		/// </param>
		/// <returns>
		/// Returns a System.Windows.Media.Media3D.GeometryModel3D representing the sphere.
		/// </returns>
		public static GeometryModel3D MakeSphere(double radius, Color color)
		{
			return MakeSphere(radius, 16, 8, color);
		}

		/// <summary>
		/// Generates a cylinder of given radius, length, resolution, and color.
		/// </summary>
		/// <param name="radius">
		/// The radius of the cylinder.
		/// </param>
		/// <param name="length">
		/// The length of the cylinder along its Y-dimension.
		/// </param>
		/// <param name="slices">
		/// The number of divisions around the cylinder's central axis.
		/// </param>
		/// <param name="color">
		/// The color of the cylinder.
		/// </param>
		/// <returns>
		/// Returns a System.Windows.Media.Media3D.GeometryModel3D representing the cylinder.
		/// </returns>
		public static GeometryModel3D MakeCylinder(double radius, double length, int slices,
			Color color)
		{
			List<Point3D> positions = new List<Point3D>();
			List<int> indices = new List<int>();
			double deltaTheta = 2 * Math.PI / slices;
			int lastIndex = 1 + 4 * slices;
			double halfLength = length / 2;

			positions.Add(new Point3D(0, halfLength, 0));

			for (int s = 0; s < slices; s++)
			{
				double theta = s * deltaTheta;
				double x = radius * Math.Cos(theta);
				double z = radius * Math.Sin(theta);

				positions.Add(new Point3D(x, halfLength, z));
				positions.Add(new Point3D(x, halfLength, z));
				positions.Add(new Point3D(x, -halfLength, z));
				positions.Add(new Point3D(x, -halfLength, z));

				int nextS = (s + 1) % slices;

				// Compute indices
				int i0 = 4 * nextS + 1;
				int i1 = 4 * s + 1;
				int i2 = 4 * s + 2;
				int i3 = 4 * nextS + 2;
				int i4 = 4 * nextS + 3;
				int i5 = 4 * s + 3;
				int i6 = 4 * s + 4;
				int i7 = 4 * nextS + 4;

				// Add indices
				indices.AddRange(new int[]
				{
					0, i0, i1,
					i2, i3, i4,
					i4,	i5, i2,
					lastIndex, i6, i7
				});
			}

			positions.Add(new Point3D(0, -halfLength, 0));

			return new GeometryModel3D(
				new MeshGeometry3D
				{
					Positions = new Point3DCollection(positions),
					TriangleIndices = new Int32Collection(indices)
				},
				new DiffuseMaterial(new SolidColorBrush(color)));
		}

		/// <summary>
		/// Generates a 16-slice cylinder of given radius, length, and color.
		/// </summary>
		/// <param name="radius">
		/// The radius of the cylinder.
		/// </param>
		/// <param name="length">
		/// The length of the cylinder along its Y-dimension.
		/// </param>
		/// <param name="color">
		/// The color of the cylinder.
		/// </param>
		/// <returns>
		/// Returns a System.Windows.Media.Media3D.GeometryModel3D representing the cylinder.
		/// </returns>
		public static GeometryModel3D MakeCylinder(double radius, double length, Color color)
		{
			return MakeCylinder(radius, length, 16, color);
		}
		#endregion
	}
}
